package jp.co.nes.awssupport.access.dao;

import java.time.YearMonth;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;

import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.bean.CompileProgressBean;
import jp.co.nes.awssupport.common.utility.AwsDynamoDBHelper;
import jp.co.nes.awssupport.common.utility.DateUtil;

/**
 * 集計進捗状況DAOです。
 */
public class CompileProgressDao {

    /**
     * デフォルトコンストラクタです。
     */
    public CompileProgressDao() {
    }

    /**
     * テーブルを作成します。
     *
     * @param readCapacity ReadCapacityUnits
     * @param writeCapacity WriteCapacityUnits
     */
    public void createTable(long readCapacity, long writeCapacity) {
        AwsDynamoDBHelper helper = new AwsDynamoDBHelper();
        helper.createTable(CompileProgressBean.class, readCapacity, writeCapacity);
    }

    /**
     * 集計進捗状況リスト(全件)を返却します。<br>
     * 該当データが存在しない場合は、空のリストを返却します。
     *
     * @return 集計進捗状況リスト
     */
    public List<CompileProgressBean> getList() {
        return getList(false);
    }

    /**
     * 集計進捗状況リスト(全件)をConsistentReadで読み込み、返却します。<br>
     * 該当データが存在しない場合は、空のリストを返却します。
     *
     * @return 集計進捗状況リスト
     */
    public List<CompileProgressBean> getListWithConsistentRead() {
        return getList(true);
    }

    /**
     * 引数に該当する集計進捗状況リストをConsistentReadで読み込み、返却します。<br>
     * 該当データが存在しない場合は、空のリストを返却します。
     *
     * @param targetYearMonth 処理対象年月
     * @return 集計進捗状況リスト
     */
    public List<CompileProgressBean> getListWithConsistentRead(YearMonth targetYearMonth) {
        return getList(targetYearMonth, true);
    }

    /**
     * 引数に該当する集計進捗状況を返却します。<br>
     * 該当データが存在しない場合は、nullを返却します。
     *
     * @param linkedAccountId 連結アカウントID
     * @param targetYearMonth 処理対象年月
     * @return 集計進捗状況
     */
    public CompileProgressBean get(String linkedAccountId, YearMonth targetYearMonth) {
        return get(linkedAccountId, targetYearMonth, false);
    }

    /**
     * 引数に該当する集計進捗状況をConsistentReadで読み込み、返却します。<br>
     * 該当データが存在しない場合は、nullを返却します。
     *
     * @param linkedAccountId 連結アカウントID
     * @param targetYearMonth 処理対象年月
     * @return 集計進捗状況
     */
    public CompileProgressBean getWithConsistentRead(String linkedAccountId, YearMonth targetYearMonth) {
        return get(linkedAccountId, targetYearMonth, true);
    }

    /**
     * 集計進捗状況を保存します。
     *
     * @param bean 集計進捗状況
     */
    public void save(CompileProgressBean bean) {
        AwsDynamoDBHelper helper = new AwsDynamoDBHelper();
        helper.save(bean);
    }

    /**
     * 集計進捗状況リストを保存します。
     *
     * @param list 集計進捗状況リスト
     */
    public void save(List<CompileProgressBean> list) {
        list.stream().parallel().forEach(bean -> {
            // parallelで処理を実施する。
            AwsDynamoDBHelper helper = new AwsDynamoDBHelper();
            helper.save(bean);
        });
    }

    /**
     * 集計進捗状況リストを削除します。
     *
     * @param list 集計進捗状況リスト
     */
    public void delete(CompileProgressBean bean) {
        AwsDynamoDBHelper helper = new AwsDynamoDBHelper();
        helper.delete(bean);
    }

    /**
     * 集計進捗状況リストを削除します。
     *
     * @param list 集計進捗状況リスト
     */
    public void delete(List<CompileProgressBean> list) {
        list.stream().parallel().forEach(bean -> {
            // parallelで処理を実施する。
            AwsDynamoDBHelper helper = new AwsDynamoDBHelper();
            helper.delete(bean);
        });
    }

    /**
     * 集計進捗状況リスト(全件)を返却します。<br>
     * 該当データが存在しない場合は、空のリストを返却します。
     *
     * @param isConsistentRead true: ConsistentReadで読み込む, false: ConsistentReadで読み込まない。
     * @return 集計進捗状況リスト
     */
    private List<CompileProgressBean> getList(boolean isConsistentRead) {
        DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
        scanExpression.setConsistentRead(isConsistentRead);

        // ローカル環境のDynamoDBに対して、1回の処理対象年月におけるデータ件数を2000件とし、それ2つ分登録した状態で
        // 検索しても1秒未満だったので、性能的に特に問題ないと判断し、scanを使用。
        AwsDynamoDBHelper helper = new AwsDynamoDBHelper();
        List<CompileProgressBean> scanList = helper.scan(CompileProgressBean.class, scanExpression);
        if (scanList == null) {
            scanList = new ArrayList<>();
        }
        return scanList;
    }

    /**
     * 引数に該当する集計進捗状況リストを返却します。<br>
     * 該当データが存在しない場合は、空のリストを返却します。
     *
     * @param targetYearMonth 処理対象年月
     * @param isConsistentRead true: ConsistentReadで読み込む, false: ConsistentReadで読み込まない。
     * @return 集計進捗状況リスト
     */
    private List<CompileProgressBean> getList(YearMonth targetYearMonth, boolean isConsistentRead) {
        String filter = "targetYearMonth = :targetYearMonth";
        DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
        scanExpression.setFilterExpression(filter);
        scanExpression.setConsistentRead(isConsistentRead);

        Map<String, AttributeValue> attrValMap = new HashMap<>();
        attrValMap.put(":targetYearMonth", new AttributeValue().withS(DateUtil.toString(targetYearMonth)));
        scanExpression.setExpressionAttributeValues(attrValMap);

        // ローカル環境のDynamoDBに対して、1回の処理対象年月におけるデータ件数を2000件とし、それ2つ分登録した状態で
        // 検索しても1秒未満だったので、性能的に特に問題ないと判断し、scanを使用。
        AwsDynamoDBHelper helper = new AwsDynamoDBHelper();
        List<CompileProgressBean> scanList = helper.scan(CompileProgressBean.class, scanExpression);
        if (scanList == null) {
            scanList = new ArrayList<>();
        }
        return scanList;
    }

    /**
     * 引数に該当する集計進捗状況を返却します。<br>
     * 該当データが存在しない場合は、nullを返却します。
     *
     * @param linkedAccountId 連結アカウントID
     * @param targetYearMonth 処理対象年月
     * @param isConsistentRead true: ConsistentReadで読み込む, false: ConsistentReadで読み込まない。
     * @return 集計進捗状況
     */
    private CompileProgressBean get(String linkedAccountId, YearMonth targetYearMonth, boolean isConsistentRead) {
        String keyConditionExpression = "linkedAccountId = :linkedAccountId and targetYearMonth = :targetYearMonth";
        DynamoDBQueryExpression<CompileProgressBean> queryExpression = new DynamoDBQueryExpression<>();
        queryExpression.setKeyConditionExpression(keyConditionExpression);
        queryExpression.setConsistentRead(isConsistentRead);

        Map<String, AttributeValue> attrValMap = new HashMap<>();
        attrValMap.put(":linkedAccountId", new AttributeValue().withS(linkedAccountId));
        attrValMap.put(":targetYearMonth", new AttributeValue().withS(DateUtil.toString(targetYearMonth)));
        queryExpression.setExpressionAttributeValues(attrValMap);

        // queryは条件にプライマリキーの設定が必要。
        CompileProgressBean result = null;
        AwsDynamoDBHelper helper = new AwsDynamoDBHelper();
        List<CompileProgressBean> queryList = helper.query(CompileProgressBean.class, queryExpression);
        if (queryList != null && !queryList.isEmpty()) {
            int size = queryList.size();
            if (size == 1) {
                result = queryList.get(0);
            } else {
                throw new CommonException("queryList is wrong(size=" + size + ")");
            }
        }
        return result;
    }
}
